use crate::{
    server::Instruction,
    util::{extract_clip_meta, ClipFormats, ClipMeta, VERSION},
    protocol::*,
};
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use clap::Args;
use clipboard_rs::{
    common::RustImage, Clipboard, ClipboardContext, ClipboardHandler, ClipboardWatcher,
    ClipboardWatcherContext, RustImageData,
};
use std::{
    io::{Error, ErrorKind, Read, Write},
    net::TcpStream,
    process::exit,
    sync::{Arc, Mutex},
    thread,
    time::Duration,
};

#[derive(Args, Clone)]
pub struct ClientArgs {
    /// join server url
    pub url: String,
    /// password
    #[arg(short, long)]
    pub password: String,
}

struct ClientContext {
    url: String,
    hash: String,
    clip_meta: ClipMeta,
    pub stop_watcher_once: bool,
    password: String,
}

impl ClientContext {
    fn new(url: String, password: String) -> Self {
        Self {
            url,
            hash: String::new(),
            clip_meta: ClipMeta::from_clips(vec![], crate::util::ClipScenes::Common),
            stop_watcher_once: false,
            password,
        }
    }
}

pub fn run(args: &ClientArgs) {
    let server_description = String::from(WELCOME_MESSAGE)
        .replace("{author}", "codimiracle")
        .replace("{version}", VERSION)
        .replace("{password}", args.password.as_str());
    println!("{}", server_description);
    println!("join url: {}\n", args.url);

    let context = ClientContext::new(args.url.clone(), args.password.clone());

    let arc = Arc::new(Mutex::new(context));
    let watcher_context = arc.clone();
    let watcher = thread::spawn(move || {
        background_task_watcher(watcher_context);
    });

    let scheduler_context = arc.clone();
    let scheduler = thread::spawn(move || loop {
        let cycle_context = scheduler_context.clone();
        thread::sleep(Duration::from_secs(5));
        sync_clipboard_if_necessary(cycle_context);
    });

    ctrlc::set_handler(move || {
        println!("clipflow terminated.");
        exit(0);
    })
    .expect("Error setting Ctrl-C handler");

    scheduler.join();
    watcher.join();
}

fn background_task_watcher(context: Arc<Mutex<ClientContext>>) {
    let handler = ClipboardClientBackgoundHandler::new(context);
    let mut watcher = ClipboardWatcherContext::new().unwrap();
    watcher.add_handler(handler);
    watcher.start_watch();
}
fn extract_data(content: &str) -> Result<String, Error> {
    let mut lines = content.lines();
    let status_line = lines.next().ok_or_else(|| Error::new(ErrorKind::Other, "Empty response"))?;
    
    if status_line.starts_with(ERROR_PREFIX) {
        return Err(Error::new(ErrorKind::PermissionDenied, status_line));
    }
    
    let mut data = String::new();
    for ele in lines {
        data.push_str(ele);
    }
    Ok(data)
}

fn sync_clipboard_if_necessary(context: Arc<Mutex<ClientContext>>) {
    if let Ok(mut locked) = context.lock() {
        match send_command(
            &locked.url,
            Instruction::ReadHash.to_string().as_str(),
            "",
            &locked.password
        ) {
            Ok(content) => {
                match extract_data(&content) {
                    Ok(hash) => {
                        if locked.hash != hash {
                            if let Ok(content) =
                                send_command(&locked.url, Instruction::ReadClips.to_string().as_str(), "", &locked.password)
                            {
                                let json = extract_data(&content).unwrap();
                                locked.hash = hash;
                                locked.clip_meta = serde_json::from_str(&json).unwrap();
                                locked.stop_watcher_once = true;
                                sync_clipboard(&content);
                                println!("{}", SYNC_MESSAGE);
                            }
                        }
                    }
                    Err(e) => println!("Authentication error: {}", e),
                }
            }
            Err(e) => println!("Connection error: {}", e),
        }
    }
}

fn sync_clipboard(content: &str) -> Result<(), Error> {
    let json = extract_data(&content).unwrap();

    let clipboard_ctx = ClipboardContext::new().unwrap();

    let clip_meta: ClipMeta = serde_json::from_str(&json).unwrap();
    for ele in clip_meta.get_clips() {
        if ele.format == ClipFormats::Text {
            clipboard_ctx.set_text(ele.files.first().unwrap().get_content());
        }
        if ele.format == ClipFormats::Html {
            clipboard_ctx.set_html(ele.files.first().unwrap().get_content());
        }
        if ele.format == ClipFormats::RichText {
            clipboard_ctx.set_rich_text(ele.files.first().unwrap().get_content());
        }
        if ele.format == ClipFormats::Image {
            let raw = ele.files.first().unwrap().get_raw();
            let decoded: Vec<u8> = URL_SAFE.decode(&raw).unwrap();
            let bytes = decoded.as_slice();
            if let Ok(image_data) = to_image_data(bytes) {
                let buffer = image_data.to_png().unwrap();
                clipboard_ctx.set_buffer("PNG", buffer.get_bytes().to_vec());
            }
        }
    }
    Ok(())
}

fn to_image_data(bytes: &[u8]) -> Result<RustImageData, Error> {
    let err = Error::new(ErrorKind::Unsupported, "Failed to parse image data");
    RustImageData::from_bytes(bytes).map_err(|_e| err)
}

fn send_command(url: &str, instruction: &str, message: &str, password: &str) -> Result<String, Error> {
    let mut content = String::new();
    content.push_str(instruction);
    content.push_str("\n");
    content.push_str(password);
    if !message.is_empty() {
        content.push_str("\n");
        content.push_str(message);
    }
    send(url, content.as_str())
}

fn send(url: &str, message: &str) -> Result<String, Error> {
    match TcpStream::connect(url) {
        Ok(mut stream) => {
            stream.write_all(message.as_bytes())?;
            stream.flush()?;

            let lines: Vec<_> = message.lines().collect();
            println!("--- meta send: {}", lines[0]);
            println!("--- meta token: {}", lines[1]);
            if lines.len() > 2 {
                println!("{}", lines[2]);
            }
            
            
            stream.shutdown(std::net::Shutdown::Write)?;
            let mut result = String::new();
            stream.read_to_string(&mut result)?;
            println!("--- server reply: {}", result);
            stream.shutdown(std::net::Shutdown::Read)?;
            Ok(result)
        }
        Err(error) => Err(error),
    }
}

pub struct ClipboardClientBackgoundHandler {
    context: Arc<Mutex<ClientContext>>,
}
impl ClipboardClientBackgoundHandler {
    fn new(context: Arc<Mutex<ClientContext>>) -> Self {
        Self { context }
    }
}

impl ClipboardHandler for ClipboardClientBackgoundHandler {
    fn on_clipboard_change(&mut self) {
        if let Ok(mut locked) = self.context.lock() {
            if locked.stop_watcher_once {
                println!("{}", SKIP_SYNC_MESSAGE);
                locked.stop_watcher_once = false;
                return;
            }

            let context = ClipboardContext::new().unwrap();
            let meta = extract_clip_meta(&context);
            if meta.is_empty() {
                println!("{}", NO_CLIPS_MESSAGE);
                return;
            }

            if let Ok(result) = send_command(
                &locked.url,
                Instruction::WriteClips.to_string().as_str(),
                serde_json::to_string(&meta).unwrap().as_str(),
                &locked.password
            ) {
                let hash = extract_data(&result).unwrap();
                locked.clip_meta = meta;
                locked.hash = hash;
                println!("{}", SYNC_MESSAGE);
            }
        }
    }
}
